/'
	===================================================================================
	
		FB.Packer - FreeBASIC Package Manager
		
		http://code.google.com/p/fbpacker/
	
	-----------------------------------------------------------------------------------
	
		version history: 
			
			1.0 (May 27, 2008) 
				- first release
			
			1.1 (May 27, 2008) 
				- added AddImage and ExtractImage methods
			
			1.2 (May 29, 2008)
				- added data integrity verification (with built-in Adler-32 checksum algorithm)
				- added compression/decompression support (using FastLZ library)
				- redistributed as static library from now on
			
			1.3 (May 30, 2008)
				- fixed memory leaks
				- removed RC4 algorithm (as it was source to some segfaults) 
				- implemented simple Scramble encryption algorithm (replace for RC4)
				- added SuperFastHash algorithm
				- removed dynarray.bi and added new list managment code
				- added ItemCount and Item properties
				- added overloads to ExtractFile/ExtractImage to support extracting files by index
			
			1.3.1 ( draft )
				- fixed some memory-related bugs (thanks to cha0s for pointing that) 
	
	
		author: 
			
			Aleksandar Ruzicic (krckoorascic@gmail.com)
		
		
		thanks to:
			
			Ariya Hidayat - for his lightning-fast FastLZ library
			hexdude - for compilling FB.Packer and FastLZ on linux
	
	-----------------------------------------------------------------------------------
	
		Copyright (c) 2008 Aleksandar Ruzicic
	
		Permission is hereby granted, free of charge, to any person obtaining a copy
		of this software and associated documentation files (the "Software"), to deal
		in the Software without restriction, including without limitation the rights
		to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
		copies of the Software, and to permit persons to whom the Software is
		furnished to do so, subject to the following conditions:
		
		The above copyright notice and this permission notice shall be included in
		all copies or substantial portions of the Software.
		
		THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
		IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
		FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
		AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
		LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
		OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
		THE SOFTWARE. 

	===================================================================================
'/

#Include "fbgfx.bi"
#Include "crt.bi"
#Include "fastlz.bi"

Namespace FB
	
	
	Declare Sub Scramble(ptrData As UByte Pointer, length As Integer, password As String)
	
	Declare Function Adler32(ptrData As UByte Pointer, length As Integer) As UInteger
	Declare Function SuperFastHash(ptrData As UByte Pointer, length As Integer) As UInteger
	
	
	Type CryptFunction As Sub(ptrData As UByte Pointer, length As Integer, password As String)
	Type ChecksumFunction As Function(ptrData As UByte Pointer, length As Integer) As UInteger
			
			
	Type PackerItem
		Name		As String
		Data		As UByte Ptr = 0
		Length	As UInteger = 0
		Compress	As Integer = -1
	End Type
	
	Type PackerItemList
		item		As PackerItem Pointer = 0
		size		As UInteger = 0
	End Type
	
	Type Packer
		
		Public:
			
			Declare Sub AddFile(Name As String, Filename As String, Compress As Integer = -1)
			Declare Sub AddFile(Name As String, pData As UByte Pointer, length As UInteger, Compress As Integer = -1)
			Declare Sub AddImage(Name As String, image As FB.IMAGE Pointer, Compress As Integer = -1)
			Declare Sub AddBMPImage(ItemName As String, image As String, Compress As Integer = -1)
			
			Declare Sub ExtractFile(Name As String, OutputFile As String)
			Declare Sub ExtractFile(Index As Integer, OutputFile As String)
			Declare Sub ExtractFile(Name As String, ByRef pData As UByte Pointer, ByRef length As UInteger)
			Declare Sub ExtractFile(Index As Integer, ByRef pData As UByte Pointer, ByRef length As UInteger)
			Declare Function ExtractImage(Name As String) As FB.IMAGE Pointer
			Declare Function ExtractImage(Index As Integer) As FB.IMAGE Pointer
			
			Declare Property ItemCount As UInteger
			
			Declare Property Item(Index As UInteger) As PackerItem Pointer
			Declare Property Item(Name As String) As PackerItem Pointer
			
			Declare Sub Remove(Name As String)
			Declare Sub Clear
			
			Declare Function Save(Filename As String, Password As String) As Integer
			Declare Function Load(Filename As String, Password As String) As Integer
			
			Encrypt	As CryptFunction = ProcPtr(Scramble)
			Decrypt	As CryptFunction = ProcPtr(Scramble)
			
			Checksum	As ChecksumFunction = ProcPtr(Adler32)
			
			Declare Constructor
			Declare Destructor
		
		Private:
			
			Declare Function IndexFromName(ByVal Name As String) As Integer
			
			items	As PackerItemList Pointer

	End Type
	
	
	'
	'  Adds new file to package, data is read from file
	'  If item with specified Name already exists it will be overwritten
	'
	Sub Packer.AddFile(ItemName As String, Filename As String, Compress As Integer = -1)
		
		Dim As Integer 			fn = FreeFile
		Dim As UByte Pointer		fileData
		
		If Open(Filename For Binary Access Read Lock Write As #fn) = 0 Then
			
			fileData = CAllocate( Lof(fn) )
			
			Get #fn, , fileData[0], Lof(fn)
			
			AddFile ItemName, fileData, Lof(fn), Compress
			
			Close #fn
			
			DeAllocate fileData
			
		EndIf
		
	End Sub
	
	'
	'  Adds new file to package, data is read from memory
	'  If item with specified Name already exists it will be overwritten
	'
	Sub Packer.AddFile(ItemName As String, pData As UByte Pointer, length As UInteger, Compress As Integer = -1)
		
		Dim As Integer		index = IndexFromName(ItemName)
		
		If index >= 0 Then
			
			With items->item[index]
				
				If .Length <> length Then 

					DeAllocate .Data
					.Data = CAllocate(length)				

				EndIf

				.Length = length
				.Compress = Compress

				memcpy .Data, pData, length

			End With
			
		Else
			
			index = items->size
			items->size += 1
			items->item = ReAllocate(items->item, SizeOf(PackerItem) * items->size)
			
			With items->item[index]
				
				.Name = LCase(ItemName)	
				.Data = CAllocate(length)
				.Length = length
				.Compress = Compress
				
				memcpy .Data, pData, length
				
			End With
			
		EndIf
		
	End Sub
	

	'
	'  Adds image to package, image is pointer to FB.IMAGE  
	'
	Sub Packer.AddImage(ItemName As String, image As FB.IMAGE Pointer, Compress As Integer = -1)
		
		AddFile ItemName, Cast(UByte Pointer, image), SizeOf(FB.IMAGE) + (image->pitch * image->height), Compress
		
	End Sub
	
	'
	'  Adds BMP image to package, image is filename of bmp
	'
	Sub Packer.AddBMPImage(ItemName As String, image As String, Compress As Integer = -1)
		
		Dim As Integer					fn = FreeFile
		Dim As FB.IMAGE Pointer		bmp = Any
		Dim As UInteger				w, h		
		
		If Open(image For Binary Access Read Lock Write As #fn) = 0 Then
			
			Get #fn, 19, w
			Get #fn, 23, h
			
			Close #fn
			
			bmp = ImageCreate(w, h)
			
			BLoad image, bmp
			
			AddFile ItemName, Cast(UByte Pointer, bmp), SizeOf(FB.IMAGE) + (bmp->pitch * bmp->height), Compress
			
			ImageDestroy bmp
			
		EndIf
		
	End Sub
	
	
	'
	'  Extracts file from package to file
	'
	Sub Packer.ExtractFile(ItemName As String, OutputFile As String)
		
		ExtractFile IndexFromName(ItemName), OutputFile
		
	End Sub
	
	Sub Packer.ExtractFile(index As Integer, OutputFile As String)
		
		If index >= 0 And index < items->size Then
			
			Dim As Integer fn = FreeFile
			
			Kill OutputFile
			
			If Open(OutputFile For Binary Access Write Lock Write As #fn) = 0 Then
				
				With items->item[index]
					
					Put #fn, , .Data[0], .Length
					
				End With
				
				Close #fn
				
			EndIf

		EndIf
		
	End Sub
	
	'
	'  Extracts file from package to memory 
	'
	Sub Packer.ExtractFile(ItemName As String, ByRef pData As UByte Pointer, ByRef length As UInteger)
		
		ExtractFile IndexFromName(ItemName), pData, length
		
	End Sub
	
	Sub Packer.ExtractFile(index As Integer, ByRef pData As UByte Pointer, ByRef length As UInteger)
		
		If index >= 0 And index < items->size Then
			
			With items->item[index]
				
				pData = CAllocate(.Length)
				length = .Length
				
				memcpy pData, .Data, length
				
			End With
			
		EndIf
		
	End Sub
	
	
	'
	'  Extracts image from package, returns pointer to FB.IMAGE (user must ImageDestroy it)
	'
	Function Packer.ExtractImage(ItemName As String) As FB.IMAGE Pointer
		
		Dim As UByte Pointer 	image
		Dim As UInteger			length
		
		ExtractFile IndexFromName(ItemName), image, length
		
		Return Cast(FB.IMAGE Pointer, image)
			
	End Function
	
	Function Packer.ExtractImage(Index As Integer) As FB.IMAGE Pointer
		
		Dim As UByte Pointer 	image
		Dim As UInteger			length
		
		ExtractFile Index, image, length
		
		Return Cast(FB.IMAGE Pointer, image)
		
	End Function
	
	'
	'  Returns number of items in package
	'
	Property Packer.ItemCount As UInteger
		
		Return items->size
	
	End Property
	
	'
	'  Returns pointer to item in package having specified index (returns 0 if index is out of bounds)
	'		
	Property Packer.Item(Index As UInteger) As PackerItem Pointer
		
		If Index < items->size Then Return items->item + index
		
	End Property
	
	'
	'  Returns pointer to item in package having specified label (returns 0 if index is out of bounds)
	'
	Property Packer.Item(ItemName As String) As PackerItem Pointer
		
		Return Item(IndexFromName(ItemName))
	
	End Property
	
	
	'
	'  Removes file from package
	'
	Sub Packer.Remove(ItemName As String)
		
		Dim As Integer		index = IndexFromName(ItemName)
		
		If index >= 0 Then
			
			DeAllocate items->item[index].Data
			
			items->size -= 1
			
			If items->size > 0 Then
			
				For i As Integer = index To items->size - 1
					items->item[i] = items->item[i + 1]
				Next
				
				items->item = ReAllocate(items->item, SizeOf(PackerItem) * items->size)			
				
			Else
				
				DeAllocate items->item
				
				items->item = 0
				
			EndIf
						
		EndIf
		
	End Sub
	
	
	'
	'  Removes all files from package
	'
	Sub Packer.Clear
		
		If items->item <> 0 Then
		
			For i As Integer = 0 To items->size - 1
				
				DeAllocate items->item[i].Data
				
				items->item[i].Data = 0
				
			Next
			
			DeAllocate items->item
			
			items->item = 0
			items->size = 0
			
		EndIf
		
	End Sub
	
	
	#Define FB_PACKER_FORMAT_HEADER		"FBPCK"
	#Define FB_PACKER_FORMAT_VERSION		CInt(&h0100)
	
	'
	'  Writes package to file and encrypts data using provided password
	'
	Function Packer.Save(Filename As String, Password As String) As Integer
		
		Dim As Integer		fn = FreeFile
		
		If Open(Filename For Binary Access Read Write Lock Write As #fn) = 0 Then
			
			Dim As String 				strBuffer
			Dim As UInteger 			size, length, deallocated = 0 
			Dim As UByte Pointer 	byteBuffer
			
			strBuffer = FB_PACKER_FORMAT_HEADER
			
			Put #fn, , strBuffer								' FBPCK format header
			length = FB_PACKER_FORMAT_VERSION
			Put #fn, , length									' FBPCK format version
			
			Put #fn, , items->size							' number of items in package
			
			For i As Integer = 0 To items->size - 1
				
				With items->item[i]
					
					Put #fn, , Len(.Name)					' label size
					
					strBuffer = .Name
					
					Encrypt(StrPtr(strBuffer), Len(.Name), Password)
					
					Put #fn, , strBuffer						' label
					
					strBuffer = String(.Length, 0)
					
					memcpy StrPtr(strBuffer), .Data, .Length
					
					Encrypt(StrPtr(strBuffer), .Length, Password)
					
					If .Compress Then
						
						If deallocated Then
							deallocated = 0
							byteBuffer = 0
						EndIf
						
						byteBuffer = ReAllocate(byteBuffer, FASTLZ_BUFFER_SIZE(.Length))
							
						size = .Length
						
						length = fastlz_compress_level(2, StrPtr(strBuffer), .Length, byteBuffer)
					
					Else
						
						If Not deallocated Then
							deallocated = Not 0
							DeAllocate byteBuffer
						EndIf
						
						byteBuffer = StrPtr(strBuffer)
						
						size = 0
						
						length = .Length
						
					EndIf
					
					Put #fn, , length							' stored data size
					Put #fn, , size							' uncompressed data size
					Put #fn, , byteBuffer[0], length		' data
					
				End With
				
			Next
			
			If deallocated Then byteBuffer = 0
			
			byteBuffer = ReAllocate(byteBuffer, Lof(fn))
			
			Get #fn, 1, byteBuffer[0], Lof(fn)
			
			Put #fn, , Checksum(byteBuffer, Lof(fn))	' hash
			
			DeAllocate byteBuffer
			
			Close #fn
			
			Return Not 0
			
		EndIf
		
		Return 0
		
	End Function
	
	
	'
	'  Loads package from file and decrypts data using provided password
	'
	Function Packer.Load(Filename As String, Password As String) As Integer
		
		Dim As Integer		fn = FreeFile
		
		If Open(Filename For Binary Access Read Lock Write As #fn) = 0 Then
			
			Dim As String				strBuffer
			Dim As UInteger 			intBuffer, size, deallocated = 0
			Dim As UByte Pointer 	byteBuffer
			
			Clear
			
			size = Lof(fn) - SizeOf(Integer)
			byteBuffer = CAllocate(size)
			
			Get #fn, , byteBuffer[0], size
			Get #fn, , intBuffer										' hash
			
			If Checksum(byteBuffer, size) <> intBuffer Then
				
				DeAllocate byteBuffer
				
				Close #fn
				
				Return 0
				
			EndIf
			
			strBuffer = Space(Len(FB_PACKER_FORMAT_HEADER))
			
			Seek #fn, 1
			
			Get #fn, , *StrPtr(strBuffer), Len(FB_PACKER_FORMAT_HEADER)		' header
			
			If strBuffer = FB_PACKER_FORMAT_HEADER Then
				
				Get #fn, , intBuffer														' format version
				
				If intBuffer = FB_PACKER_FORMAT_VERSION Then
					
					Get #fn, , items->size												' number of items in package
					
					items->item = CAllocate(items->size * SizeOf(PackerItem))
					
					For i As Integer = 0 To items->size - 1

						With items->item[i]
						
							Get #fn, , intBuffer											' label size
							
							strBuffer = Space(intBuffer)
							
							Get #fn, , *StrPtr(strBuffer), intBuffer				' label
							
							Decrypt(StrPtr(strBuffer), intBuffer, Password)
							
							.Name = strBuffer
							
							Get #fn, , intBuffer											' stored data size
							Get #fn, , size												' uncompressed data size
							
							strBuffer = Space(intBuffer)
							
							Get #fn, , *StrPtr(strBuffer), intBuffer				' data
							
							If size > 0 Then
								
								If deallocated Then
									deallocated = 0
									byteBuffer = 0
								EndIf
								
								byteBuffer = ReAllocate(byteBuffer, size)
								
								If fastlz_decompress(StrPtr(strBuffer), intBuffer, byteBuffer, size) = 0 Then
									
									DeAllocate byteBuffer
									Return 0
									
								EndIf 
									
							Else
								
								If Not deallocated Then
									deallocated = Not 0
									DeAllocate byteBuffer
								EndIf
								
								byteBuffer = StrPtr(strBuffer)
								
								size = intBuffer
									
							EndIf
							
							Decrypt(byteBuffer, size, Password)
							
							.Length = size
							
							.Data = CAllocate(.Length)
							
							memcpy .Data, byteBuffer, .Length
						
						End With
						
					Next
					
					DeAllocate byteBuffer
					
					Return Not 0
					
				EndIf
				
			EndIf
			
			Close #fn
			
		EndIf
		
		Return 0
		
	End Function
	
	
	'
	'  Internal procedure - returns index of item with specified Name or -1 if none found
	'
	Function Packer.IndexFromName(ByVal ItemName As String) As Integer
		
		ItemName = LCase(ItemName)
		
		For i As Integer = 0 To items->size - 1
			
			If items->item[i].Name = ItemName Then Return i 
			
		Next
		
		Return -1
		
	End Function
	
	'
	'  Allocate needed memory
	'
	Constructor Packer
	
		items = New PackerItemList
		
	End Constructor
	
	'
	'  Clears memory...
	'
	Destructor Packer
			
		Clear
		
		If items <> 0 Then Delete items
	
	End Destructor
	
	
	#Include "crypt.bas"
	#Include "checksum.bas"
	

End Namespace